// m_blocks.go
package bmodel

import (
	"fmt"
	"strconv"
	"time"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

/*
 * count the number of blocks
 * para: none
 * return int, error
 */
func Countblocks() (int, error) {
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	if dberr != nil {
		panic(dberr)
		return -1, fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}

	var blocks []Block
	var countSize int
	tx := db.Find(&blocks)
	if tx.Error == nil {
		tx = tx.Count(&countSize)
	}
	return countSize, nil
}

/*
 * get toplevel blockIDs and blockNames and state = 1(发布)
 * para: none
 * return []map[string]string, error
 */
func GetPublishedToplevelBlockIdsAndNames() ([]map[string]string, error) {
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	res := make([]map[string]string, 0)
	if dberr != nil {
		panic(dberr)
		return res, fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}

	var blocks []Block
	db.Select([]string{"id", "title"}).Where("parentid = 0 and state = 1").Find(&blocks)
	for _, v := range blocks {
		tempmap := make(map[string]string)
		tempmap["Id"] = strconv.Itoa(v.Id)
		tempmap["Title"] = v.Title
		res = append(res, tempmap)
	}

	res = append(res, BLOCK_ROOT_MAP)
	return res, nil
}

/*
 * get sublevel blockIDs and blockNames and state = 1(发布)
 * para: none
 * return map[string][]map[string]string, error
 * 注：返回的是以顶级板块标题分类的底层板块ID和标题
 * 这样的结构：
 * map[string][]map[string]string, 其中每一个顶级map的键都是顶级板块标题，里面是一个切片，每一个值都是一个底层板块map
 *
 * 备注：这个函数里面部分地方没有对返回值error判断，将在下一版本修改，最好改成sql语句直接查询解决
 */
func GetPublishedSublevelBlockIdsAndNames() (map[string][]map[string]string, error) {
	ret := make(map[string][]map[string]string)
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()

	if dberr != nil {
		panic(dberr)
		return ret, fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}

	topblockinfos, terr := GetPublishedToplevelBlockIdsAndNames()
	if terr == nil {
		relaOfIdAndTitle := make(map[string]string)
		for _, v := range topblockinfos {
			if v["Id"] != strconv.Itoa(BLOCK_ROOT_ID) {
				tempmap := make([]map[string]string, 0)
				ret[v["Title"]] = tempmap
				relaOfIdAndTitle[v["Id"]] = v["Title"]
			}
		}

		var blocks []Block
		db.Select([]string{"id", "parentid", "title"}).Where("parentid != 0 and state = 1").Find(&blocks)

		for _, v := range blocks {
			if checkIfTopLevelBlock(v.Parentid) == nil {
				tempmap := make(map[string]string)
				tempParentidString := strconv.Itoa(v.Parentid)
				tempmap["Id"] = strconv.Itoa(v.Id)
				tempmap["Title"] = v.Title
				ret[relaOfIdAndTitle[tempParentidString]] = append(ret[relaOfIdAndTitle[tempParentidString]], tempmap)
			}
		}
		return ret, nil
	} else {
		return ret, terr
	}
}

/*
 * check if this block is virtual root block or it is a toplevel block
 * para: id int
 * return: error
 */
func checkIfTopLevelBlock(id int) error {
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	if dberr != nil {
		return fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}
	if id == BLOCK_ROOT_ID {
		return nil
	} else {
		var blocks []Block
		if db.Where(&Block{Id: id, Parentid: BLOCK_ROOT_ID}).Find(&blocks); len(blocks) == 1 {
			return nil
		} else {
			return fmt.Errorf(BLOCK_NOT_TOP_LEVEL)
		}
	}
}

/*
 * check if this block is a sublevel block
 * para: id int
 * return: error
 */
func checkIfSubLevelBlock(id int) error {
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	if dberr != nil {
		return fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}
	var blocks []Block
	if db.Where(&Block{Id: id, Parentid: BLOCK_ROOT_ID}).Find(&blocks); len(blocks) == 1 {
		parentId := blocks[0].Parentid
		if errc := checkIfTopLevelBlock(parentId); errc != nil {
			return fmt.Errorf(BLOCK_NOT_SUB_LEVEL)
		} else {
			return nil
		}
	} else {
		return fmt.Errorf(BLOCK_NOT_SUB_LEVEL)
	}
}

/*
 * get block by its ID
 * para: id int
 * return string, error
 */
func GetblockNameById(id int) (string, error) {
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	res := ""
	if dberr != nil {
		return res, fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}

	var blocks []Block
	if db.Select("title").Where(&Block{Id: id}).Find(&blocks); len(blocks) == 1 {
		for _, v := range blocks {
			res = v.Title
		}
		return res, nil
	} else {
		return res, fmt.Errorf(ERROR_BLOCK_ID)
	}
}

/*
 * get block by its ID
 * para: id int
 * return map[string]string, error
 */
func GetblockById(id int) (map[string]string, error) {
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	res := make(map[string]string)
	if dberr != nil {
		panic(dberr)
		return res, fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}

	var blocks []Block
	if db.Where(&Block{Id: id}).Find(&blocks); len(blocks) == 1 {
		for _, v := range blocks {
			res["Id"] = strconv.Itoa(v.Id)
			res["Parentid"] = strconv.Itoa(v.Parentid)
			res["Title"] = v.Title
			res["Describe"] = v.Describe
			res["State"] = strconv.Itoa(v.State)
			res["Reserve"] = v.Reserve
			res["Addtime"] = v.Addtime.Format(time.RFC1123)
			res["Edittime"] = v.Edittime.Format(time.RFC1123)
		}
		return res, nil
	} else {
		return res, fmt.Errorf(ERROR_BLOCK_ID)
	}
}

/*
 * get blocks by limit: pageindex and num of problems of one page
 * para: pageindex, numofpage int
 * return []map[string]string, error
 */
func GetblocksByPage(pageindex, numofpage int) ([]map[string]string, error) {
	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	res := make([]map[string]string, 0)
	if dberr != nil {
		panic(dberr)
		return res, fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}

	//numofpage check if valid, 和problem 公用一套判断, 下一版本修改
	if (numofpage > MAX_NUMBER_OF_INFO_IN_ONE_PAGE) || (numofpage <= MIN_NUMBER_OF_INFO_IN_ONE_PAGE) {
		res := make([]map[string]string, 1)
		return res, fmt.Errorf(ERROR_NUMOFPAGE_OUT_OF_RANGE)
	}

	var blocks []Block
	db.Offset((pageindex - 1) * numofpage).Limit(numofpage).Find(&blocks)
	for _, v := range blocks {
		tempmap := make(map[string]string)
		tempmap["Id"] = strconv.Itoa(v.Id)
		tempmap["Parentid"] = strconv.Itoa(v.Parentid)
		tempmap["Title"] = v.Title
		tempmap["Describe"] = v.Describe
		tempmap["State"] = strconv.Itoa(v.State)
		tempmap["Reserve"] = v.Reserve
		tempmap["Addtime"] = v.Addtime.Format(time.RFC1123)
		tempmap["Edittime"] = v.Edittime.Format(time.RFC1123)
		res = append(res, tempmap)
	}
	return res, nil
}

/*
 * add block
 * para: blockParentid, blockTitle, blockDiscribe, blockState
 * return error
 */
func Addblock(blockParentid, blockTitle, blockDiscribe, blockState string) error {
	var blockMap = map[string]string{
		"Parentid": blockParentid,
		"Title":    blockTitle,
		"Describe": blockDiscribe,
		"State":    blockState,
		"Reserve":  "default",
	}

	var fdata = make(map[string]interface{}, 0)
	if errf := firstFilter("block", blockMap, &fdata); errf != nil {
		return errf
	}

	pStruct := Block{}
	if errs := secondFilter("block", fdata, &pStruct); errs != nil {
		return errs
	}

	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	if dberr != nil {
		return fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}
	if errc := db.Create(&pStruct).Error; errc != nil {
		return fmt.Errorf(ERROR_BLOCK_CANNOT_BE_CREATED)
	} else {
		return nil
	}
}

/*
 * edit block by its ID
 * para: id int, blockParentid, blockTitle, blockDiscribe, blockState string
 * return error
 */
func EditblockById(id int, blockParentid, blockTitle, blockDiscribe,
	blockState string) error {

	var blockMap = map[string]string{
		"Parentid": blockParentid,
		"Title":    blockTitle,
		"Describe": blockDiscribe,
		"State":    blockState,
	}

	var fdata = make(map[string]interface{}, 0)
	if errf := firstFilter("block", blockMap, &fdata); errf != nil {
		return errf
	}

	db, dberr := gorm.Open("mysql", DB_CONNECT_STR)
	defer db.Close()
	if dberr != nil {
		return fmt.Errorf(ERROR_CANNOT_OPEN_DB)
	}
	var blocks []Block
	if db.Where(&Block{Id: id}).Find(&blocks); len(blocks) == 1 {
		db.Where(&Block{Id: id}).Model(&blocks).Updates(fdata)
		return nil
	} else {
		return fmt.Errorf(ERROR_BLOCK_NOT_FOUND)
	}
}

/*
 * check function for block:parentid
 * para: s string(raw data)
 * return int, error
 * check if parentid is a toplevel block id or 0 exists
 */
func checkIfBlockparentidValid(s string) (int, error) {
	if sInt, erro := strconv.Atoi(s); erro != nil {
		return 0, erro
	} else {
		if errc := checkIfTopLevelBlock(sInt); errc != nil {
			return 0, errc
		} else {
			return sInt, nil
		}
	}
}

/*
 * get block default settings
 * para: none
 * return map[string]string
 */
func GetblockDefaultSettings() map[string]map[string]map[string]string {
	return BLOCK_CHOOSES_TABLE
}

func TEST_bModel_block() {
	topblocks, aerr := GetPublishedToplevelBlockIdsAndNames()
	fmt.Println(topblocks)
	fmt.Println(aerr)

	subblocks, aerr2 := GetPublishedSublevelBlockIdsAndNames()
	fmt.Println(subblocks)
	fmt.Println(aerr2)
}
